package com.fw.service.bouserauthentication.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.fw.service.applicationlog.ApplicationLogService;
import com.fw.service.applicationlog.bean.ApplicationLogBean;
import com.fw.service.auditlog.AuditLogService;
import com.fw.service.auditlog.bean.AuditLogBean;
import com.fw.service.bean.EntryBean;
import com.fw.service.bean.ErrorBean;
import com.fw.service.bouserauthentication.BOUserAuthenticationService;
import com.fw.service.bouserauthentication.bean.BOUserAuthenticationBean;
import com.fw.service.message.MessageService;
import com.fw.service.passwordpolicy.PasswordPolicyService;
import com.fw.service.passwordpolicy.bean.PasswordPolicyBean;
import com.fw.service.persistence.BOUserPersistenceService;
import com.fw.service.persistence.dao.BOUserLoginDAO;
import com.fw.service.persistence.dao.RoleDAO;
import com.fw.service.persistence.dto.BOUser;
import com.fw.service.persistence.dto.BOUserLogin;
import com.fw.service.persistence.dto.Role;
import com.fw.service.property.PropertyService;
import com.fw.service.securitylog.SecurityLogService;
import com.fw.service.securitylog.bean.SecurityLogBean;
import com.fw.util.DateUtils;
import com.fw.util.PasswordUtils;
import com.fw.util.StringUtils;

public class BOUserAuthenticationServiceImpl implements BOUserAuthenticationService {

    @Autowired
    private PropertyService propertyService;

    @Autowired
    private MessageService messageService;

    @Autowired
    private BOUserPersistenceService boUserPersistenceService;

    @Autowired
    private BOUserLoginDAO boUserLoginDAO;

    @Autowired
    private RoleDAO roleDAO;

    @Autowired
    private PasswordPolicyService passwordPolicyService;

    @Autowired
    private AuditLogService auditLogService;

    @Autowired
    private SecurityLogService securityLogService;

    @Autowired
    private ApplicationLogService applicationLogService;

    /**
     * // BO USER
     * 
     * // 1. Check for valid username and password.
     * 
     * // 2. Check against authentication policy.
     * 
     * // 3. Check merchant status. (not validate)
     * 
     * // 4. Check user staus.
     */

    @Transactional
    @Override
    public List<EntryBean<String, Serializable>> authenticate(String username, String password, String userIp) {
	List<EntryBean<String, Serializable>> entryBeans = null;
	String clazz = BOUserAuthenticationService.class.getName();
	String method = "authenticate";
	Locale locale = Locale.ENGLISH;

	try {
	    if (username != null && password != null) {
		entryBeans = new ArrayList<EntryBean<String, Serializable>>();
		List<ErrorBean> errorBeans = new ArrayList<ErrorBean>();
		BOUserAuthenticationBean boUserAuthenticationBean = null;

		Date currentDate = DateUtils.getCurrentDate();
		String currentDateStr = StringUtils.formatDate(currentDate, StringUtils.PATTERN_DATETIME_PERSISTENCE);

		List<BOUser> boUsers = boUserPersistenceService.selectByUsernameAndStatus(username, null);
		if (boUsers == null || boUsers.isEmpty() || boUsers.size() != 1) {
		    // not found or more than one record
		    ErrorBean errorBean = new ErrorBean();
		    errorBean.setErrorCode("errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.invalid");
		    errorBeans.add(errorBean);

		    SecurityLogBean securityLogBean = new SecurityLogBean();
		    securityLogBean.setType(SecurityLogBean.TYPE_INFO);
		    securityLogBean.setClazz(BOUserAuthenticationService.class.getName());
		    securityLogBean.setMethod(method);
		    securityLogBean.setMessage(messageService.getMessage(
			    "errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.invalid", locale));
		    securityLogService.createSecurityLog(securityLogBean);
		} else {
		    // found
		    BOUser boUser = boUsers.get(0);
		    Long userId = boUser.getId();

		    // get password Policy
		    PasswordPolicyBean passwordPolicyBean = passwordPolicyService.getPasswordPolicy();

		    if (!PasswordUtils.validatePassword(password, boUser.getPasswordHash())) {
			// password fail

			// create or update userLogin attempt
			BOUserLogin boUserLogin = boUserLoginDAO.findByUserId(userId);
			if (boUserLogin == null) {
			    boUserLogin = new BOUserLogin();
			    boUserLogin.setUserId(userId);
			    boUserLogin.setLoginAttemptCount(0);
			    boUserLogin.setLoginCount(0);
			}
			boUserLogin.setLoginAttemptCount(boUserLogin.getLoginAttemptCount() + 1);
			boUserLoginDAO.save(boUserLogin);

			// get password Policy loginAttemptAllowed
			Integer loginAttemptAllowed = passwordPolicyBean.getLoginAttemptAllowed();

			// check login attempt
			if (boUserLogin != null && loginAttemptAllowed != null && boUserLogin.getLoginAttemptCount() >= loginAttemptAllowed) {
			    // update user
			    if (boUserLogin.getLoginAttemptCount() == loginAttemptAllowed) {
				boUser.setStatus(BOUser.STATUS_LOCKED);
				boUser.setLastUpdateBy("SYSTEM");
				boUser.setLastUpdateDatetime(currentDateStr);
				boUser = boUserPersistenceService.save(boUser);

				// create audit log
				String logActionType = propertyService
					.getProperty("auditlog.com.fw.service.bouser.BOUserService.updateBOUser");
				if (logActionType != null) {
				    AuditLogBean auditLogBean = new AuditLogBean();
				    auditLogBean.setLogActionType(logActionType);
				    auditLogBean.setDescription(propertyService
					    .getProperty("auditlog.com.fw.service.bouser.BOUserService.updateBOUser.status"));
				    auditLogBean.setGroupId(null);
				    auditLogBean.setUserId(null);
				    auditLogBean.setDateTime(currentDateStr);
				    auditLogBean.setUserIp(userIp);
				    auditLogService.createAuditLog(auditLogBean);
				}
			    }

			    ErrorBean errorBean = new ErrorBean();
			    errorBean
				    .setErrorCode("errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.failedAttempt");
			    errorBeans.add(errorBean);

			    SecurityLogBean securityLogBean = new SecurityLogBean();
			    securityLogBean.setType(SecurityLogBean.TYPE_INFO);
			    securityLogBean.setClazz(clazz);
			    securityLogBean.setMethod(method);
			    securityLogBean.setMessage(messageService.getMessage(
				    "errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.failedAttempt",
				    locale));
			    securityLogService.createSecurityLog(securityLogBean);
			} else {
			    ErrorBean errorBean = new ErrorBean();
			    errorBean
				    .setErrorCode("errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.invalid");
			    errorBeans.add(errorBean);

			    SecurityLogBean securityLogBean = new SecurityLogBean();
			    securityLogBean.setType(SecurityLogBean.TYPE_INFO);
			    securityLogBean.setClazz(clazz);
			    securityLogBean.setMethod(method);
			    securityLogBean.setMessage(messageService.getMessage(
				    "errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.invalid", locale));
			    securityLogService.createSecurityLog(securityLogBean);
			}
		    } else {

			Date expiredDate = StringUtils.parseDate(boUser.getExpiredDate(), StringUtils.PATTERN_DATE_PERSISTENCE);
			if (BOUser.STATUS_CLOSED.equals(boUser.getStatus()) || BOUser.STATUS_INACTIVE.equals(boUser.getStatus())
				|| BOUser.STATUS_LOCKED.equals(boUser.getStatus())) {
			    ErrorBean errorBean = new ErrorBean();
			    errorBean
				    .setErrorCode("errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.notActive");
			    errorBeans.add(errorBean);

			    SecurityLogBean securityLogBean = new SecurityLogBean();
			    securityLogBean.setType(SecurityLogBean.TYPE_INFO);
			    securityLogBean.setClazz(clazz);
			    securityLogBean.setMethod(method);
			    securityLogBean
				    .setMessage(messageService
					    .getMessage(
						    "errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.notActive",
						    locale));
			    securityLogService.createSecurityLog(securityLogBean);
			} else if (BOUser.PASSWORD_STATUS_FORCE_CHANGE.equals(boUser.getPasswordStatus())) {
			    boUserAuthenticationBean = new BOUserAuthenticationBean();
			    boUserAuthenticationBean.setUserId(boUser.getId());

			    ErrorBean errorBean = new ErrorBean();
			    errorBean
				    .setErrorCode("errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.forceChange");
			    errorBeans.add(errorBean);

			    SecurityLogBean securityLogBean = new SecurityLogBean();
			    securityLogBean.setType(SecurityLogBean.TYPE_INFO);
			    securityLogBean.setClazz(clazz);
			    securityLogBean.setMethod(method);
			    securityLogBean.setMessage(messageService.getMessage(
				    "errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.forceChange",
				    locale));
			    securityLogService.createSecurityLog(securityLogBean);
			} else if (expiredDate != null && currentDate.after(expiredDate)
				|| BOUser.STATUS_EXPIRED.equals(boUser.getStatus())) {
			    boUserAuthenticationBean = new BOUserAuthenticationBean();
			    boUserAuthenticationBean.setUserId(boUser.getId());

			    // check expired date now!
			    if (expiredDate != null && currentDate.after(expiredDate)) {
				boUser.setStatus(BOUser.STATUS_EXPIRED);
				boUser.setLastUpdateBy("SYSTEM");
				boUser.setLastUpdateDatetime(currentDateStr);
				boUser = boUserPersistenceService.save(boUser);

				// create audit log
				String logActionType = propertyService
					.getProperty("auditlog.com.fw.service.bouser.BOUserService.updateBOUser");
				if (logActionType != null) {
				    AuditLogBean auditLogBean = new AuditLogBean();
				    auditLogBean.setLogActionType(logActionType);
				    auditLogBean.setDescription(propertyService
					    .getProperty("auditlog.com.fw.service.bouser.BOUserService.updateBOUser.status"));
				    auditLogBean.setGroupId(null);
				    auditLogBean.setUserId(null);
				    auditLogBean.setDateTime(currentDateStr);
				    auditLogBean.setUserIp(userIp);
				    auditLogService.createAuditLog(auditLogBean);
				}
			    }

			    ErrorBean errorBean = new ErrorBean();
			    errorBean
				    .setErrorCode("errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.expire");
			    errorBeans.add(errorBean);

			    SecurityLogBean securityLogBean = new SecurityLogBean();
			    securityLogBean.setType(SecurityLogBean.TYPE_INFO);
			    securityLogBean.setClazz(clazz);
			    securityLogBean.setMethod(method);
			    securityLogBean.setMessage(messageService.getMessage(
				    "errors.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate.expire", locale));
			    securityLogService.createSecurityLog(securityLogBean);
			} else {
			    boUserAuthenticationBean = new BOUserAuthenticationBean();
			    boUserAuthenticationBean.setUserId(boUser.getId());
			    boUserAuthenticationBean.setUsername(boUser.getUsername());
			    boUserAuthenticationBean.setRoleName(boUser.getRole());
			    Long roleId = null;
			    Role role = roleDAO.findByRoleName(boUser.getRole());
			    if (role != null) {
				roleId = role.getId();
			    }
			    boUserAuthenticationBean.setRoleId(roleId);
			    boUserAuthenticationBean.setEmployeeName(boUser.getEmployeeName());
			    boUserAuthenticationBean.setUserStatus(boUser.getStatus());
			    // reset login attempt count and login count, last login date
			    Date lastLoginDate = null;
			    BOUserLogin boUserLogin = boUserLoginDAO.findByUserId(userId);
			    if (boUserLogin != null) {
				boUserLogin.setLoginAttemptCount(0);
				boUserLogin.setLoginCount(boUserLogin.getLoginCount() == null ? 1 : boUserLogin.getLoginCount() + 1);
				if (boUserLogin.getLoginDateTime() != null) {
				    lastLoginDate = StringUtils.parseDate(boUserLogin.getLoginDateTime(),
					    StringUtils.PATTERN_DATETIME_PERSISTENCE);
				    boUserLogin.setLastLoginDateTime(boUserLogin.getLoginDateTime());
				}
				boUserLogin.setLoginDateTime(currentDateStr);
			    } else {
				// first time login
				boUserLogin = new BOUserLogin();
				boUserLogin.setUserId(userId);
				boUserLogin.setLoginAttemptCount(0);
				boUserLogin.setLoginCount(boUserLogin.getLoginCount() == null ? 1 : boUserLogin.getLoginCount() + 1);
				boUserLogin.setLoginDateTime(currentDateStr);
			    }
			    boUserLoginDAO.save(boUserLogin);
			    boUserAuthenticationBean.setLastLoginDate(lastLoginDate);

			    // get password Policy loginAttemptAllowed
			    Integer passwordNotificationExpireDay = passwordPolicyBean.getPasswordNotificationExpireDay();
			    Long diffDays = DateUtils.diffDays(currentDate, expiredDate);
			    if (passwordNotificationExpireDay != null && diffDays <= passwordNotificationExpireDay && expiredDate != null) {
				boUserAuthenticationBean.setPasswordNotificationExpire(Boolean.TRUE);
				boUserAuthenticationBean.setDiffDays(diffDays);
			    } else {
				boUserAuthenticationBean.setPasswordNotificationExpire(Boolean.FALSE);
			    }

			    // create audit log
			    String logActionType = propertyService
				    .getProperty("auditlog.com.fw.service.bouserauthentication.BOUserAuthenticationService.authenticate");
			    if (logActionType != null) {
				AuditLogBean auditLogBean = new AuditLogBean();
				auditLogBean.setLogActionType(logActionType);
				auditLogBean.setDescription(boUser.getUsername() + " Logined.");
				auditLogBean.setGroupId(roleId);
				auditLogBean.setUserId(userId);
				auditLogBean.setDateTime(currentDateStr);
				auditLogBean.setUserIp(userIp);
				auditLogService.createAuditLog(auditLogBean);
			    }

			}
			entryBeans.add(new EntryBean<String, Serializable>(BOUserAuthenticationBean.class.getName(),
				(Serializable) boUserAuthenticationBean));
		    }
		}
		if (errorBeans != null && !errorBeans.isEmpty()) {
		    entryBeans.add(new EntryBean<String, Serializable>(List.class.getName() + "." + ErrorBean.class.getName(),
			    (Serializable) errorBeans));
		}
	    }

	} catch (Exception e) {
	    ApplicationLogBean applicationLogBean = new ApplicationLogBean();
	    applicationLogBean.setType(ApplicationLogBean.TYPE_ERROR);
	    applicationLogBean.setClazz(clazz);
	    applicationLogBean.setMethod(method);
	    applicationLogBean.setMessage(e.getMessage());
	    applicationLogService.createApplicationLog(applicationLogBean);
	}
	return entryBeans;
    }
}
